﻿//////////////////////////////////////////////////////////////////////
// <copyright file=CardinalCurve.cs">
//     Copyright (c) 2010
//     Released under  GNU GENERAL PUBLIC LICENSE
//     http://dev.perl.org/licenses/gpl1.html
// </copyright>
// <date> 3/3/2010 12:34:18 AM </date>
// <author> B. Endrovski </author>
// <web> http://furiouspixels.blogspot.com/ </web>
//////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.ComponentModel;
using System.Xml.Serialization;

namespace Curves
{
	public enum SplineType { Normal, Clamped, Closed }

	public class CardinalCurve : Curve
	{				
		[Category("Shape")]
		[Description("Elastic tension of the cardinal curve")]
		public float Tension
		{
			get	{ return tension; }
			set {
				tension = value;
				Rebuild();
			}
		}
		[XmlElement("tension")]
		protected float tension;

		[Category("Shape")]
		[Description("Behaivor of the ending points of the curve")]
		public SplineType CurveType
		{
			get { return curveType; }
			set {
				curveType = value;
				Rebuild();
			}
		}
		[XmlElement("curvetype")]
		private SplineType curveType = SplineType.Normal;

		// Control Vertices
		[XmlArray("CV")]
		public List<PointF> CV;
		
		// Polynomials for interpolation of the x coordinate
		[XmlIgnore]
		protected List<CubicPolynomial> Px;
		
		// Polynomials for interpolation of the y coordinate
		[XmlIgnore]
		protected List<CubicPolynomial> Py;

		// Contruct an emtpy curve
		public CardinalCurve() : base()
		{					
			CV = new List<PointF>(256);
			Px = new List<CubicPolynomial>(254);
			Py = new List<CubicPolynomial>(254);
			//
			tension = 0;
		}

		// Creates a new polynimal for a segment from four control vertices
		private CubicPolynomial CalculatePolynomial(float p0, float p1, float p2, float p3)
		{
			float p1Der = 0.5f * (1 - tension) * (p2 - p0);
			float p2Der = 0.5f * (1 - tension) * (p3 - p1);
			//
			float a = 2*p1 + p1Der - 2*p2 + p2Der;
			float b = -3*p1 - 2*p1Der + 3*p2 - p2Der;
			float c = p1Der;
			float d = p1;
			//
			return new CubicPolynomial(a, b, c, d);
		}

		/// <summary>
		/// Gets a CV index within a radius of the given point
		/// </summary>
		public int GetCV(Point p, float radius)
		{
			for (int i = 0; i < CV.Count; i++)
			{
				PointF cv = CV[i];
				if (Math.Sqrt((p.X - cv.X) * (p.X - cv.X) + (p.Y - cv.Y) * (p.Y - cv.Y)) < radius)
					return i;
			}
			return -1;
		}

		public void MoveCV(int idx, PointF loc)
		{
			CV[idx] = loc;
			Rebuild();
		}

		public void Rebuild()
		{
			// Minimum for creating a segment
			if (CV.Count < 4)
				return;

			Px.Clear();
			Py.Clear();			

			int start = 1;
			int end = CV.Count - 2;
			//
			switch (curveType)
			{
				case SplineType.Normal:
					start = 1;
					end = CV.Count - 2;
					break;

				case SplineType.Clamped:
					start = 0;
					end = CV.Count - 1;
					break;

				case SplineType.Closed:
					start = 0;
					end = CV.Count;
					break;
			}

			// Got through all the CVs
			for (int i = start; i < end; i++)
			{
				int prev = (i - 1) < 0 ? 0 : (i - 1);
				int next = i + 1;
				int last = (i + 2) > (CV.Count - 1) ? (i + 1) : (i + 2);

				if (curveType == SplineType.Closed)
				{
					prev = (i - 1) < 0 ? (CV.Count - 1) : (i - 1);				
					next = (i + 1) % CV.Count;							// Cycle the CVs
					last = (i + 2) % CV.Count;							// Cycle the CVs
				}

				float p0 = CV[prev].X;
				float p1 = CV[i].X;
				float p2 = CV[next].X;
				float p3 = CV[last].X;			
				//
				Px.Add( CalculatePolynomial(p0, p1, p2, p3));
				
				p0 = CV[prev].Y;
				p1 = CV[i].Y;
				p2 = CV[next].Y;
				p3 = CV[last].Y;
				//
				Py.Add(CalculatePolynomial(p0, p1, p2, p3));
			}
		}

		public override PointF[] GetCurve()
		{
			int count = Px.Count;					// No. of polys
			PointF[] samplePoints = new PointF[samples * count];
			float dt = 1.0f / (samples  - 1);		// Samples per segment
			
			for (int i = 0; i < count; i++)
			{
				float t = 0;
				for (int j = 0; j < samples; j++)
				{
					float x = Px[i].Value(t);
					float y = Py[i].Value(t);
					samplePoints[i * samples + j] = new PointF(x, y);
					t += dt;
				}
			}

			return samplePoints;
		}
	}
}
